package com.jzhao.gper.spring.v3.context;

import com.jzhao.gper.spring.v3.annotation.JZAutowired;
import com.jzhao.gper.spring.v3.annotation.JZController;
import com.jzhao.gper.spring.v3.annotation.JZService;
import com.jzhao.gper.spring.v3.aop.JZJdkDynamicAopProxy;
import com.jzhao.gper.spring.v3.aop.config.JZAopConfig;
import com.jzhao.gper.spring.v3.aop.support.JZAdvisedSupport;
import com.jzhao.gper.spring.v3.beans.JZBeanWrapper;
import com.jzhao.gper.spring.v3.beans.config.JZBeanDefinition;
import com.jzhao.gper.spring.v3.beans.support.JZBeanDefinitionReader;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author jzhao
 * @date 2020/4/9 21:32
 */
public class JZApplicationContext {
    private JZBeanDefinitionReader reader;

    private Map<String,JZBeanDefinition> beanDefinitionMap = new HashMap<String,JZBeanDefinition>();

    private Map<String,JZBeanWrapper> factoryBeanInstanceCache = new HashMap<String,JZBeanWrapper>();
    private Map<String,Object> factoryBeanObjectCache = new HashMap<String,Object>();

    public JZApplicationContext(String... configLocations) {
        reader = new JZBeanDefinitionReader(configLocations);
        try{
            List<JZBeanDefinition> beanDefinitions =  reader.loadBeanDefinitions();

            //缓存BeanDefinition
            doRegistBeanDefinition(beanDefinitions);

            doAutowrited();
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    private void doAutowrited() {
        for (Map.Entry<String, JZBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            getBean(beanName);
        }
    }

    public Object getBean(String beanName) {
        JZBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //反射实例化
        Object instance = instantiateBean(beanName,beanDefinition);
        //封装成BeanWrapper
        JZBeanWrapper beanWrapper = new JZBeanWrapper(instance);
        //保存到IOC容器
        factoryBeanInstanceCache.put(beanName,beanWrapper);
        //执行依懒注入
        populateBean(beanName,beanDefinition,beanWrapper);

        return beanWrapper.getWrapperInstance();


    }

    private void populateBean(String beanName, JZBeanDefinition beanDefinition, JZBeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrapperInstance();

        Class<?> clazz = beanWrapper.getWrappedClass();
        //如果不是controller、service就不需要依懒注入
        if(!(clazz.isAnnotationPresent(JZController.class)||clazz.isAnnotationPresent(JZService.class))){
            return ;
        }

        for (Field field : clazz.getDeclaredFields()) {
            if(!field.isAnnotationPresent(JZAutowired.class)){ continue; }

            JZAutowired autowired= field.getAnnotation(JZAutowired.class);
            String autowiredBeanName = autowired.value().trim();
            if("".equals(autowiredBeanName)){

                autowiredBeanName = field.getType().getName();
            }

            field.setAccessible(true);

            if(this.factoryBeanInstanceCache.get(autowiredBeanName)==null){
                continue;
            }

            try {
                field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

    private Object instantiateBean(String beanName, JZBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try{
            if(this.factoryBeanObjectCache.containsKey(beanName)){
                instance = this.factoryBeanObjectCache.get(beanName);
            }else{
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();

                /**************AOP begin******************/
                JZAdvisedSupport config= instantionAopConfig(beanDefinition);
                config.setTarget(instance);
                config.setTargetClass(clazz);

                if(config.pointCutMatch()){
                    instance = new JZJdkDynamicAopProxy(config).getProxy();
                }

                /**************AOP end******************/

                this.factoryBeanObjectCache.put(beanName,instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }

    private JZAdvisedSupport instantionAopConfig(JZBeanDefinition beanDefinition) {
        JZAopConfig config  = new JZAopConfig();
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        return new JZAdvisedSupport(config);


    }

    private void doRegistBeanDefinition(List<JZBeanDefinition> beanDefinitions) throws Exception {
        for (JZBeanDefinition beanDefinition : beanDefinitions) {
            if(this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The "+beanDefinition.getFactoryBeanName() + "is exsits");
            }
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(),beanDefinition);
        }
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }


    public String[] getBeanDefinitionNames(){
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }
}
